#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<sys/wait.h>
#include<assert.h>
#include<string.h>
#include<errno.h>


#define NUM 1024
#define OPT_NUM 64//最多可以被切成成64个字符串

#define NONE_REDIR 0 //没有重定向
#define INPUT_REDIR 1
#define OUTPUT_REDIR 2
#define APPEND_REDIR 3
#define isSpace(start) do{\
    while(isspace(*start)) ++start; }while(0)

char lineCommand[NUM];
char* myargv[OPT_NUM];//定义一个指针数组

int redirType = NONE_REDIR;//重定向类型
char* redirFile = NULL;

void commandCheck(char* commands)
{
    assert(commands);
    char* start = commands;
    char* end = commands + strlen(commands);

    while(start < end)
    {
        if(*start == '>')//  >  >>
        {
            *start = '\0';
            start++;
            if(*start == '>')
            {
                redirType = APPEND_REDIR;
                start++;
            }
            else
            {
                redirType = OUTPUT_REDIR;
            }
            isSpace(start);
            redirFile = start;
        }
        else if(*start == '<')
        {
            //"cat <   log.txt"
            *start = '\0';
            start++;
            isSpace(start);
            //填写重定向信息
            redirType = INPUT_REDIR;
            redirFile = start;
            break;
        }
        else
        {
            start++;
        }
    }
}
int main()
{
  while(1)
  {
        //初始化
      redirType = NONE_REDIR;//重定向类型
      redirFile = NULL;
      //输出提示符
      printf("用户名@主机名 当前路径# ");
      fflush(stdout);
      //获取用户输入，输入的时候，输入\0
      
      char* s = fgets(lineCommand,sizeof(lineCommand)-1,stdin);
      //防止用户输入为空
      assert(s != NULL);
      //现在把用户输入的\0改为0
      lineCommand[strlen(lineCommand)-1] = 0;
      //printf("test:%s\n",lineCommand);
        
    
      //"ls -a -l" -> "ls" "-a" "-l" 
      //"ls -a -l -i > myfile.txt" --> "ls -a -l -i" "myfile.txt"  如果重定向的话要把这段字符串切割成两部分(把重定向符号转换成\0)
      
      commandCheck(lineCommand);
      //字符串切割
      myargv[0] = strtok(lineCommand," ");//以空格切割
      //如果没有字串了，strtok返回NULL，myargv[end] = NULL
      //如果是cd命令，不需要创建子进程，让shell自己指向对应的命令，本子就是执行系统接口
      //像这种不需要让我们的子进程来执行，而是让shell自己执行的命令 ---> 内建/内置命令
      if(myargv[0] != NULL && strcmp(myargv[0],"cd") == 0)
      {
        if(myargv[1] != NULL) chdir(myargv[1]);
        continue;
      }
      int i = 1;
      while(myargv[i++] = strtok(NULL," "));//如果想再次切割，前面必须填NULL
    
    //测试是否成功，条件编译
    #ifdef DEBUG
      for(i = 0;myargv[i];i++)
      {
        printf("myargv[%d]:%s\n",i,myargv[i]);
      }
    #endif

      //执行命令
      pid_t id = fork();
      assert(id != -1);
      if(id == 0)
      {
        //因为命令是子进程执行的，真正重定向的工作一定是要子进程来完成
        //如果重定向，是父进程要给子进程提供信息的
        //这个重定向不会影响父进程,进程具有独立性
        switch(redirType)
        {
            case NONE_REDIR:
                //什么都不做
                break;
            case INPUT_REDIR:
                {
                    int fd = open(redirFile,O_RDONLY);
                    if(fd < 0)
                    {
                        perror("open");
                        exit(errno);
                    }
                    //重定向的文件已经打开成功
                    dup2(fd,0);
                }
                break;
            case OUTPUT_REDIR:
            case APPEND_REDIR:
                {
                    umask(0);
                    int flags = O_WRONLY | O_CREAT;
                    if(redirType == APPEND_REDIR)
                        flags |= O_APPEND;
                    else
                        flags |= O_TRUNC;
                    int fd = open(redirFile,flags,0666);
                    if(fd < 0)
                    {
                        perror("open");
                        exit(errno);
                    }
                    dup2(fd,1);
                }
                break;
            default:
                printf("bug?\n");
                break;
        }

        execvp(myargv[0],myargv);//选用execvp最合适
        //文件替换不会影响子进程曾经打开的文件。
        exit(1);
      }
      waitpid(id,NULL,0);//返回方式不关心就写NULL
  }
  return 0;

}

//显示进程：
//  ps -axj
//  ls /proc/ -al (exe:当前进程执行的是磁盘路径下的哪一个程序)(cwd:当前进程的工作目录)

//当前工作目录可以更改：chdir

//fork()->子进程 cd -> 子进程都有自己的工作路径 -》 更改的是子进程的目录！-> 子进程执行完毕 -> 继续用父进程，即shell。
